package cn.net.mugui.net.udp;

import java.io.IOException;
import java.net.DatagramSocket;
import java.util.LinkedList;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;

import org.apache.commons.lang3.StringUtils;

import com.mugui.base.base.Autowired;
import com.mugui.base.client.net.classutil.DataSave;

import cn.hutool.cache.impl.WeakCache;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.HexUtil;
import lombok.Getter;
import lombok.Setter;

public class UdpServer {

	private final ConcurrentHashMap<String, byte[]> send_map = new ConcurrentHashMap<>();
	private static final WeakCache<String, byte[]> receive_map = new WeakCache<>(60000);
	static {
		receive_map.schedulePrune(60000);
	}
	public static ThreadFactory threadFactory = null;
	public static ReceiveListener defaultListener = null;
	static WeakHashMap<Integer, ReceiveListener> receiveListeners = new WeakHashMap<>();
	UdpBagSendRunnable udpBagSendRunnable = null;

	/**
	 * 去向地址
	 */
	@Getter
	@Setter
	private String to_host;
	/**
	 * 去向端口
	 */
	@Getter
	@Setter
	private int to_port;
	@Getter
	@Setter
	private int out_time = 10000;

	/**
	 * @param to_host 去向地址
	 * @param to_port 去向端口
	 */
	public UdpServer(String to_host, int to_port) {
		if (StringUtils.isBlank(to_host) || to_port < 1) {
			throw new RuntimeException(to_host + ":" + to_port + " 地址错误");
		}
		DataSave.context.putBean(this);
		this.to_host = to_host;
		this.to_port = to_port;
	}

	public static void setDefaultReceiveListener(ReceiveListener receiveListener) {
		UdpServer.defaultListener = receiveListener;
	}

	public void send(DatagramSocket socket, UdpBag bag, int type) {
		if (threadFactory == null)
			synchronized (UdpServer.class) {
				if (threadFactory == null)
					threadFactory = ThreadUtil.createThreadFactoryBuilder().setNamePrefix("UDPServer:").build();
			}
		if (udpBagSendRunnable == null)
			synchronized (this) {
				if (udpBagSendRunnable == null) {
					threadFactory.newThread(udpBagSendRunnable = new UdpBagSendRunnable()).start();
				}
			}
		udpBagSendRunnable.add(socket, bag, type);
	}

	public void setReceiveListener(ReceiveListener receiveListener) {
		UdpServer.receiveListeners.put(receiveListener.hashCode(), receiveListener);
	}

	public void removeReceiveListener(ReceiveListener receiveListener) {
		UdpServer.receiveListeners.remove(receiveListener.hashCode());
	}

	/**
	 * 反馈包的处理
	 *
	 * @param resolve
	 * @throws IOException
	 * @author 木鬼
	 * @param socket
	 */
	public void receive(DatagramSocket socket, UdpBag resolve) throws IOException {
		switch (resolve.getType()) {
		case UdpBag.type_0:// 收到ping 消息，将连接更新为有效
			UdpBag bag = new UdpBag();
			bag.setType(UdpBag.type_1);
			if (resolve.getFrom() != null) {
				bag.setFrom(resolve.getTo());
				bag.setTo(resolve.getFrom());
			}
			sendBag(socket, bag);
			if (!receiveListeners.isEmpty()) {
				for (ReceiveListener receiveListener : receiveListeners.values()) {
					if (receiveListener != null) {
						if (receiveListener.ping(socket, this, resolve)) {
							break;
						}
					}
				}
			}
			if (defaultListener != null && defaultListener.ping(socket, this, resolve)) {
				break;
			}
			break;
		case UdpBag.type_1:// 收到ret_ping消息。若有回调，则调用回调
			if (!receiveListeners.isEmpty()) {
				for (ReceiveListener receiveListener : receiveListeners.values()) {
					if (receiveListener != null) {
						if (receiveListener.retPing(socket, this, resolve)) {
							break;
						}
					}
				}
			}
			if (defaultListener != null && defaultListener.retPing(socket, this, resolve)) {
				break;
			}
			break;

//		case UdpBag.type_2:// 接收新的hash请求的包
//			receiveBagHash(resolve);
//			break;
//		case UdpBag.type_3://接收到了3消息，发送正式包
//			sendBag(resolve);
//			break;
		case UdpBag.type_4:
		case UdpBag.type_5:

			String encodeHexStr = HexUtil.encodeHexStr(resolve.getBag_hash());
			receiveSucess(socket, resolve);
			synchronized (receive_map) {
				byte[] bs = receive_map.get(encodeHexStr);
				if (bs == null) {// 处理该包
					receive_map.put(encodeHexStr, bs = new byte[0]);
					System.out.println(encodeHexStr + " 接收到的数据:" + resolve);
					if (!receiveListeners.isEmpty()) {
						for (ReceiveListener receiveListener : receiveListeners.values()) {
							if (receiveListener != null) {
								if (receiveListener.sucess(socket, this, resolve)) {
									break;
								}
							}
						}
					}
					if (defaultListener != null && defaultListener.sucess(socket, this, resolve)) {
						break;
					}
				}
			}
			break;
		case UdpBag.type_6:// 数据包接收成功

			encodeHexStr = HexUtil.encodeHexStr(resolve.getBag_hash());
			System.out.println("send_Sucess->" + encodeHexStr);
			byte[] remove = send_map.remove(encodeHexStr);
			if (remove != null)
				synchronized (remove) {
					remove.notifyAll();
				}
			break;
		}
	}

	/**
	 * 反馈接收到了这个bag的hash信息
	 *
	 * @param udpBag
	 * @throws IOException
	 * @author 木鬼
	 */
	private void receiveSucess(DatagramSocket socket, UdpBag udpBag) throws IOException {
		System.out.println("receiveSucess->" + HexUtil.encodeHexStr(udpBag.getBag_hash()));
		UdpBag bag = new UdpBag();
		bag.setBag_hash(udpBag.getBag_hash());
		bag.setType(UdpBag.type_6);
		bag.setFrom(udpBag.getTo());
		bag.setTo(udpBag.getFrom());
		sendBag(socket, bag);
	}

	private void sendBag(DatagramSocket socket, UdpBag bag) throws IOException {
		baseServer.send(socket, bag.bytes(), to_host, to_port);
	}

	@Autowired
	private UdpBaseServer baseServer;

	/**
	 * 发送一个包
	 *
	 * @throws IOException
	 * @author 木鬼
	 */
	private void sendBag(UdpBagSendBean udpBagSend) throws IOException {
		// System.out.println(to_host + ":" + to_port + "发送的数据：" + bag.toString());
		baseServer.send(udpBagSend.socket, udpBagSend.bag.bytes(), to_host,
				udpBagSend.type == 0 ? to_port : check_port);
	}

	public void close() {
		if (udpBagSendRunnable != null)
			udpBagSendRunnable.stop();
		if (receiveListeners != null)
			receiveListeners.clear();
	}

	public interface ReceiveListener {
		/**
		 * ping消息
		 *
		 * @author 木鬼
		 */
		boolean ping(DatagramSocket socket, UdpServer server, UdpBag resolve);

		/**
		 * 反馈ping消息
		 *
		 * @author 木鬼
		 */
		boolean retPing(DatagramSocket socket, UdpServer server, UdpBag resolve);

		/**
		 * 接收到的数据
		 * 
		 * @param socket
		 */
		boolean sucess(DatagramSocket socket, UdpServer server, UdpBag resolve);

	}

	private class UdpBagSendBean {
		public UdpBagSendBean(DatagramSocket socket2, UdpBag bag2, int type) {
			this.bag = bag2;
			this.socket = socket2;
			this.type = type;
		}

		/**
		 * 0 基本类型 1测试
		 */
		int type;
		UdpBag bag;
		DatagramSocket socket;
	}

	/**
	 * 发送包的处理
	 *
	 * @author 木鬼
	 */
	private class UdpBagSendRunnable implements Runnable {

		LinkedList<UdpBagSendBean> arrayList = new LinkedList<>();

		private boolean bool = true;

		public void add(DatagramSocket socket, UdpBag bag, int type) {
			synchronized (this) {
				if (arrayList.size() > 32) {
					try {
						this.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				arrayList.add(new UdpBagSendBean(socket, bag, type));
				this.notifyAll();
			}
		}

		@Override
		public void run() {
			while (bool) {
				UdpBagSendBean udpBag = arrayList.poll();
				if (udpBag == null) {
					synchronized (this) {
						udpBag = arrayList.poll();
						if (udpBag == null) {
							try {
								this.wait();
								continue;
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
				}
				try {
					handle(udpBag);
				} catch (Throwable e) {
					e.printStackTrace();
				}
				synchronized (this) {
					this.notifyAll();
				}
			}
		}

		public void stop() {
			synchronized (this) {
				bool = false;
				this.notify();
			}
		}

		/**
		 *
		 * @throws IOException
		 */
		private void handle(UdpBagSendBean udpBagSend) throws IOException {
			UdpBag udpBag = udpBagSend.bag;
			switch (udpBag.getType()) {
			case UdpBag.type_4:
			case UdpBag.type_5: {
				String encodeHexStr = HexUtil.encodeHexStr(udpBag.getBag_hash());
				byte[] lock = null;
				send_map.put(encodeHexStr, lock = new byte[0]);
				receive_map.put(encodeHexStr, lock);
				synchronized (lock) {
					long time = System.currentTimeMillis();
					while (out_time == 0 || (System.currentTimeMillis() - time < out_time)) {
						if (send_map.get(encodeHexStr) != null) {
							sendBag(udpBagSend);
							try {
								lock.wait(1000);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						} else
							break;

					}
				}
				if (send_map.get(encodeHexStr) != null) {
					send_map.remove(encodeHexStr);
					// 超时：
					throw new RuntimeException("关于地址：" + to_host + ":" + to_port + "发送包数据超时");
				}
			}
				break;
			default:
				sendBag(udpBagSend);
				break;
			}

		}

	}

	@Setter
	Integer check_port = null;
}
